package com.lwlk.util.tuple;

import java.io.Serializable;
import java.util.*;

import java.util.stream.StreamSupport;

public final class Tuple6<T1, T2, T3, T4, T5, T6> 
    implements Iterable<Object>,
               Serializable,
               Comparable<Tuple6> {
    
    private static final int size = 6;
    
    private final T1 value1;
	private final T2 value2;
	private final T3 value3;
	private final T4 value4;
	private final T5 value5;
	private final T6 value6;
    
    public Tuple6(final T1 arg1, final T2 arg2, final T3 arg3, final T4 arg4, final T5 arg5, final T6 arg6) {
        this.value1 = arg1;
		this.value2 = arg2;
		this.value3 = arg3;
		this.value4 = arg4;
		this.value5 = arg5;
		this.value6 = arg6;
    }
    
    public static <T1, T2, T3, T4, T5, T6> Tuple6<T1, T2, T3, T4, T5, T6> with(final T1 arg1, final T2 arg2, final T3 arg3, final T4 arg4, final T5 arg5, final T6 arg6) {
        return new Tuple6<>(arg1, arg2, arg3, arg4, arg5, arg6);
    }

    public static <X> Tuple6<X, X, X, X, X, X> fromArray(final X[] array) {
        if (array == null) {
            throw new IllegalArgumentException("Array cannot be null");
        }
        if (array.length != 6) {
            throw new IllegalArgumentException("Array must have exactly 6 elements in order to create this Tuple. Size is " + array.length);
        }      
        return new Tuple6<>(array[0], array[1], array[2], array[3], array[4], array[5]);
    }
    
    public static <X> Tuple6<X, X, X, X, X, X> fromCollection(final Collection<X> coll) {
        return fromIterable(coll);
    }
    
    public static <X> Tuple6<X, X, X, X, X, X> fromIterable(final Iterable<X> iterable) {
        return fromIterable(iterable, 0);
    }
    
    @SuppressWarnings("unchecked")
    public static <X> Tuple6<X, X, X, X, X, X> fromIterable(final Iterable<X> iterable, final int index) {
        if (iterable == null) {
            throw new IllegalArgumentException("Iterable cannot be null");
        }
        return fromArray((X[]) StreamSupport.stream(iterable.spliterator(), false).skip(index).toArray());
    }
    
    public <X> Tuple6<X, T2, T3, T4, T5, T6> setAt1(final X arg) { return new Tuple6<>(arg,value2,value3,value4,value5,value6); }
	public <X> Tuple6<T1, X, T3, T4, T5, T6> setAt2(final X arg) { return new Tuple6<>(value1,arg,value3,value4,value5,value6); }
	public <X> Tuple6<T1, T2, X, T4, T5, T6> setAt3(final X arg) { return new Tuple6<>(value1,value2,arg,value4,value5,value6); }
	public <X> Tuple6<T1, T2, T3, X, T5, T6> setAt4(final X arg) { return new Tuple6<>(value1,value2,value3,arg,value5,value6); }
	public <X> Tuple6<T1, T2, T3, T4, X, T6> setAt5(final X arg) { return new Tuple6<>(value1,value2,value3,value4,arg,value6); }
	public <X> Tuple6<T1, T2, T3, T4, T5, X> setAt6(final X arg) { return new Tuple6<>(value1,value2,value3,value4,value5,arg); }
    
    public Tuple5<T2, T3, T4, T5, T6> removeAt1() { return new Tuple5<>(value2, value3, value4, value5, value6); }
	public Tuple5<T1, T3, T4, T5, T6> removeAt2() { return new Tuple5<>(value1, value3, value4, value5, value6); }
	public Tuple5<T1, T2, T4, T5, T6> removeAt3() { return new Tuple5<>(value1, value2, value4, value5, value6); }
	public Tuple5<T1, T2, T3, T5, T6> removeAt4() { return new Tuple5<>(value1, value2, value3, value5, value6); }
	public Tuple5<T1, T2, T3, T4, T6> removeAt5() { return new Tuple5<>(value1, value2, value3, value4, value6); }
	public Tuple5<T1, T2, T3, T4, T5> removeAt6() { return new Tuple5<>(value1, value2, value3, value4, value5); }
    
    public <X1> Tuple7<X1, T1, T2, T3, T4, T5, T6> insertArgsAt0(final X1 arg1) { return new Tuple7<>(arg1, value1, value2, value3, value4, value5, value6); }
	public <X1> Tuple7<T1, X1, T2, T3, T4, T5, T6> insertArgsAt1(final X1 arg1) { return new Tuple7<>(value1, arg1, value2, value3, value4, value5, value6); }
	public <X1> Tuple7<T1, T2, X1, T3, T4, T5, T6> insertArgsAt2(final X1 arg1) { return new Tuple7<>(value1, value2, arg1, value3, value4, value5, value6); }
	public <X1> Tuple7<T1, T2, T3, X1, T4, T5, T6> insertArgsAt3(final X1 arg1) { return new Tuple7<>(value1, value2, value3, arg1, value4, value5, value6); }
	public <X1> Tuple7<T1, T2, T3, T4, X1, T5, T6> insertArgsAt4(final X1 arg1) { return new Tuple7<>(value1, value2, value3, value4, arg1, value5, value6); }
	public <X1> Tuple7<T1, T2, T3, T4, T5, X1, T6> insertArgsAt5(final X1 arg1) { return new Tuple7<>(value1, value2, value3, value4, value5, arg1, value6); }
	public <X1> Tuple7<T1, T2, T3, T4, T5, T6, X1> insertArgsAt6(final X1 arg1) { return new Tuple7<>(value1, value2, value3, value4, value5, value6, arg1); }
	public <X1, X2> Tuple8<X1, X2, T1, T2, T3, T4, T5, T6> insertArgsAt0(final X1 arg1, final X2 arg2) { return new Tuple8<>(arg1, arg2, value1, value2, value3, value4, value5, value6); }
	public <X1, X2> Tuple8<T1, X1, X2, T2, T3, T4, T5, T6> insertArgsAt1(final X1 arg1, final X2 arg2) { return new Tuple8<>(value1, arg1, arg2, value2, value3, value4, value5, value6); }
	public <X1, X2> Tuple8<T1, T2, X1, X2, T3, T4, T5, T6> insertArgsAt2(final X1 arg1, final X2 arg2) { return new Tuple8<>(value1, value2, arg1, arg2, value3, value4, value5, value6); }
	public <X1, X2> Tuple8<T1, T2, T3, X1, X2, T4, T5, T6> insertArgsAt3(final X1 arg1, final X2 arg2) { return new Tuple8<>(value1, value2, value3, arg1, arg2, value4, value5, value6); }
	public <X1, X2> Tuple8<T1, T2, T3, T4, X1, X2, T5, T6> insertArgsAt4(final X1 arg1, final X2 arg2) { return new Tuple8<>(value1, value2, value3, value4, arg1, arg2, value5, value6); }
	public <X1, X2> Tuple8<T1, T2, T3, T4, T5, X1, X2, T6> insertArgsAt5(final X1 arg1, final X2 arg2) { return new Tuple8<>(value1, value2, value3, value4, value5, arg1, arg2, value6); }
	public <X1, X2> Tuple8<T1, T2, T3, T4, T5, T6, X1, X2> insertArgsAt6(final X1 arg1, final X2 arg2) { return new Tuple8<>(value1, value2, value3, value4, value5, value6, arg1, arg2); }
	public <X1, X2, X3> Tuple9<X1, X2, X3, T1, T2, T3, T4, T5, T6> insertArgsAt0(final X1 arg1, final X2 arg2, final X3 arg3) { return new Tuple9<>(arg1, arg2, arg3, value1, value2, value3, value4, value5, value6); }
	public <X1, X2, X3> Tuple9<T1, X1, X2, X3, T2, T3, T4, T5, T6> insertArgsAt1(final X1 arg1, final X2 arg2, final X3 arg3) { return new Tuple9<>(value1, arg1, arg2, arg3, value2, value3, value4, value5, value6); }
	public <X1, X2, X3> Tuple9<T1, T2, X1, X2, X3, T3, T4, T5, T6> insertArgsAt2(final X1 arg1, final X2 arg2, final X3 arg3) { return new Tuple9<>(value1, value2, arg1, arg2, arg3, value3, value4, value5, value6); }
	public <X1, X2, X3> Tuple9<T1, T2, T3, X1, X2, X3, T4, T5, T6> insertArgsAt3(final X1 arg1, final X2 arg2, final X3 arg3) { return new Tuple9<>(value1, value2, value3, arg1, arg2, arg3, value4, value5, value6); }
	public <X1, X2, X3> Tuple9<T1, T2, T3, T4, X1, X2, X3, T5, T6> insertArgsAt4(final X1 arg1, final X2 arg2, final X3 arg3) { return new Tuple9<>(value1, value2, value3, value4, arg1, arg2, arg3, value5, value6); }
	public <X1, X2, X3> Tuple9<T1, T2, T3, T4, T5, X1, X2, X3, T6> insertArgsAt5(final X1 arg1, final X2 arg2, final X3 arg3) { return new Tuple9<>(value1, value2, value3, value4, value5, arg1, arg2, arg3, value6); }
	public <X1, X2, X3> Tuple9<T1, T2, T3, T4, T5, T6, X1, X2, X3> insertArgsAt6(final X1 arg1, final X2 arg2, final X3 arg3) { return new Tuple9<>(value1, value2, value3, value4, value5, value6, arg1, arg2, arg3); }
	public <X1, X2, X3, X4> Tuple10<X1, X2, X3, X4, T1, T2, T3, T4, T5, T6> insertArgsAt0(final X1 arg1, final X2 arg2, final X3 arg3, final X4 arg4) { return new Tuple10<>(arg1, arg2, arg3, arg4, value1, value2, value3, value4, value5, value6); }
	public <X1, X2, X3, X4> Tuple10<T1, X1, X2, X3, X4, T2, T3, T4, T5, T6> insertArgsAt1(final X1 arg1, final X2 arg2, final X3 arg3, final X4 arg4) { return new Tuple10<>(value1, arg1, arg2, arg3, arg4, value2, value3, value4, value5, value6); }
	public <X1, X2, X3, X4> Tuple10<T1, T2, X1, X2, X3, X4, T3, T4, T5, T6> insertArgsAt2(final X1 arg1, final X2 arg2, final X3 arg3, final X4 arg4) { return new Tuple10<>(value1, value2, arg1, arg2, arg3, arg4, value3, value4, value5, value6); }
	public <X1, X2, X3, X4> Tuple10<T1, T2, T3, X1, X2, X3, X4, T4, T5, T6> insertArgsAt3(final X1 arg1, final X2 arg2, final X3 arg3, final X4 arg4) { return new Tuple10<>(value1, value2, value3, arg1, arg2, arg3, arg4, value4, value5, value6); }
	public <X1, X2, X3, X4> Tuple10<T1, T2, T3, T4, X1, X2, X3, X4, T5, T6> insertArgsAt4(final X1 arg1, final X2 arg2, final X3 arg3, final X4 arg4) { return new Tuple10<>(value1, value2, value3, value4, arg1, arg2, arg3, arg4, value5, value6); }
	public <X1, X2, X3, X4> Tuple10<T1, T2, T3, T4, T5, X1, X2, X3, X4, T6> insertArgsAt5(final X1 arg1, final X2 arg2, final X3 arg3, final X4 arg4) { return new Tuple10<>(value1, value2, value3, value4, value5, arg1, arg2, arg3, arg4, value6); }
	public <X1, X2, X3, X4> Tuple10<T1, T2, T3, T4, T5, T6, X1, X2, X3, X4> insertArgsAt6(final X1 arg1, final X2 arg2, final X3 arg3, final X4 arg4) { return new Tuple10<>(value1, value2, value3, value4, value5, value6, arg1, arg2, arg3, arg4); }
    
    public <X1> Tuple7<X1, T1, T2, T3, T4, T5, T6> insertTupleAt0(final Tuple1<X1> tuple) { return new Tuple7<>(tuple.getValue1(), value1, value2, value3, value4, value5, value6); }
	public <X1> Tuple7<T1, X1, T2, T3, T4, T5, T6> insertTupleAt1(final Tuple1<X1> tuple) { return new Tuple7<>(value1, tuple.getValue1(), value2, value3, value4, value5, value6); }
	public <X1> Tuple7<T1, T2, X1, T3, T4, T5, T6> insertTupleAt2(final Tuple1<X1> tuple) { return new Tuple7<>(value1, value2, tuple.getValue1(), value3, value4, value5, value6); }
	public <X1> Tuple7<T1, T2, T3, X1, T4, T5, T6> insertTupleAt3(final Tuple1<X1> tuple) { return new Tuple7<>(value1, value2, value3, tuple.getValue1(), value4, value5, value6); }
	public <X1> Tuple7<T1, T2, T3, T4, X1, T5, T6> insertTupleAt4(final Tuple1<X1> tuple) { return new Tuple7<>(value1, value2, value3, value4, tuple.getValue1(), value5, value6); }
	public <X1> Tuple7<T1, T2, T3, T4, T5, X1, T6> insertTupleAt5(final Tuple1<X1> tuple) { return new Tuple7<>(value1, value2, value3, value4, value5, tuple.getValue1(), value6); }
	public <X1> Tuple7<T1, T2, T3, T4, T5, T6, X1> insertTupleAt6(final Tuple1<X1> tuple) { return new Tuple7<>(value1, value2, value3, value4, value5, value6, tuple.getValue1()); }
	public <X1, X2> Tuple8<X1, X2, T1, T2, T3, T4, T5, T6> insertTupleAt0(final Tuple2<X1, X2> tuple) { return new Tuple8<>(tuple.getValue1(), tuple.getValue2(), value1, value2, value3, value4, value5, value6); }
	public <X1, X2> Tuple8<T1, X1, X2, T2, T3, T4, T5, T6> insertTupleAt1(final Tuple2<X1, X2> tuple) { return new Tuple8<>(value1, tuple.getValue1(), tuple.getValue2(), value2, value3, value4, value5, value6); }
	public <X1, X2> Tuple8<T1, T2, X1, X2, T3, T4, T5, T6> insertTupleAt2(final Tuple2<X1, X2> tuple) { return new Tuple8<>(value1, value2, tuple.getValue1(), tuple.getValue2(), value3, value4, value5, value6); }
	public <X1, X2> Tuple8<T1, T2, T3, X1, X2, T4, T5, T6> insertTupleAt3(final Tuple2<X1, X2> tuple) { return new Tuple8<>(value1, value2, value3, tuple.getValue1(), tuple.getValue2(), value4, value5, value6); }
	public <X1, X2> Tuple8<T1, T2, T3, T4, X1, X2, T5, T6> insertTupleAt4(final Tuple2<X1, X2> tuple) { return new Tuple8<>(value1, value2, value3, value4, tuple.getValue1(), tuple.getValue2(), value5, value6); }
	public <X1, X2> Tuple8<T1, T2, T3, T4, T5, X1, X2, T6> insertTupleAt5(final Tuple2<X1, X2> tuple) { return new Tuple8<>(value1, value2, value3, value4, value5, tuple.getValue1(), tuple.getValue2(), value6); }
	public <X1, X2> Tuple8<T1, T2, T3, T4, T5, T6, X1, X2> insertTupleAt6(final Tuple2<X1, X2> tuple) { return new Tuple8<>(value1, value2, value3, value4, value5, value6, tuple.getValue1(), tuple.getValue2()); }
	public <X1, X2, X3> Tuple9<X1, X2, X3, T1, T2, T3, T4, T5, T6> insertTupleAt0(final Tuple3<X1, X2, X3> tuple) { return new Tuple9<>(tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), value1, value2, value3, value4, value5, value6); }
	public <X1, X2, X3> Tuple9<T1, X1, X2, X3, T2, T3, T4, T5, T6> insertTupleAt1(final Tuple3<X1, X2, X3> tuple) { return new Tuple9<>(value1, tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), value2, value3, value4, value5, value6); }
	public <X1, X2, X3> Tuple9<T1, T2, X1, X2, X3, T3, T4, T5, T6> insertTupleAt2(final Tuple3<X1, X2, X3> tuple) { return new Tuple9<>(value1, value2, tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), value3, value4, value5, value6); }
	public <X1, X2, X3> Tuple9<T1, T2, T3, X1, X2, X3, T4, T5, T6> insertTupleAt3(final Tuple3<X1, X2, X3> tuple) { return new Tuple9<>(value1, value2, value3, tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), value4, value5, value6); }
	public <X1, X2, X3> Tuple9<T1, T2, T3, T4, X1, X2, X3, T5, T6> insertTupleAt4(final Tuple3<X1, X2, X3> tuple) { return new Tuple9<>(value1, value2, value3, value4, tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), value5, value6); }
	public <X1, X2, X3> Tuple9<T1, T2, T3, T4, T5, X1, X2, X3, T6> insertTupleAt5(final Tuple3<X1, X2, X3> tuple) { return new Tuple9<>(value1, value2, value3, value4, value5, tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), value6); }
	public <X1, X2, X3> Tuple9<T1, T2, T3, T4, T5, T6, X1, X2, X3> insertTupleAt6(final Tuple3<X1, X2, X3> tuple) { return new Tuple9<>(value1, value2, value3, value4, value5, value6, tuple.getValue1(), tuple.getValue2(), tuple.getValue3()); }
	public <X1, X2, X3, X4> Tuple10<X1, X2, X3, X4, T1, T2, T3, T4, T5, T6> insertTupleAt0(final Tuple4<X1, X2, X3, X4> tuple) { return new Tuple10<>(tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), tuple.getValue4(), value1, value2, value3, value4, value5, value6); }
	public <X1, X2, X3, X4> Tuple10<T1, X1, X2, X3, X4, T2, T3, T4, T5, T6> insertTupleAt1(final Tuple4<X1, X2, X3, X4> tuple) { return new Tuple10<>(value1, tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), tuple.getValue4(), value2, value3, value4, value5, value6); }
	public <X1, X2, X3, X4> Tuple10<T1, T2, X1, X2, X3, X4, T3, T4, T5, T6> insertTupleAt2(final Tuple4<X1, X2, X3, X4> tuple) { return new Tuple10<>(value1, value2, tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), tuple.getValue4(), value3, value4, value5, value6); }
	public <X1, X2, X3, X4> Tuple10<T1, T2, T3, X1, X2, X3, X4, T4, T5, T6> insertTupleAt3(final Tuple4<X1, X2, X3, X4> tuple) { return new Tuple10<>(value1, value2, value3, tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), tuple.getValue4(), value4, value5, value6); }
	public <X1, X2, X3, X4> Tuple10<T1, T2, T3, T4, X1, X2, X3, X4, T5, T6> insertTupleAt4(final Tuple4<X1, X2, X3, X4> tuple) { return new Tuple10<>(value1, value2, value3, value4, tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), tuple.getValue4(), value5, value6); }
	public <X1, X2, X3, X4> Tuple10<T1, T2, T3, T4, T5, X1, X2, X3, X4, T6> insertTupleAt5(final Tuple4<X1, X2, X3, X4> tuple) { return new Tuple10<>(value1, value2, value3, value4, value5, tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), tuple.getValue4(), value6); }
	public <X1, X2, X3, X4> Tuple10<T1, T2, T3, T4, T5, T6, X1, X2, X3, X4> insertTupleAt6(final Tuple4<X1, X2, X3, X4> tuple) { return new Tuple10<>(value1, value2, value3, value4, value5, value6, tuple.getValue1(), tuple.getValue2(), tuple.getValue3(), tuple.getValue4()); }
    
    public Object getValue(final int pos) { 
        if(pos > getSize() || pos <= 0) {
            throw new IllegalArgumentException(
                    "Cannot retrieve position " + pos + " in " + this.getClass().getSimpleName() + 
                    ". Positions for this class start with 1 and end with " + getSize());
        }      
        switch(pos) {
            case 1: return value1;
			case 2: return value2;
			case 3: return value3;
			case 4: return value4;
			case 5: return value5;
			case 6: return value6;
        }
        return null; // just make java happy!
    }
    
    public List<Object> toList() {
        return Collections.unmodifiableList(Arrays.asList(value1, value2, value3, value4, value5, value6));
    }
    
    public Object[] toArray() {
        return new Object[] { value1, value2, value3, value4, value5, value6 };
    }

    @Override
    public Iterator<Object> iterator() { return toList().iterator(); }

    @Override
    public int hashCode() {
        return Objects.hash(value1, value2, value3, value4, value5, value6);
    }
    
    @Override
    public boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Tuple6 tmp = (Tuple6) obj;
        return this.compareTo(tmp) == 0;
    }    
    
    @Override
    public int compareTo(final Tuple6 t) {
        for(int i = 1; i <= getSize(); ++i) {
            final int comparison = ((Comparable) getValue(i)).compareTo(t.getValue(i));
            if(comparison != 0) return comparison;
        }
        return 0;
    }
    
    public int getSize() { return size; }
    
    public T1 getValue1() { return value1; }
    public T2 getValue2() { return value2; }
    public T3 getValue3() { return value3; }
    public T4 getValue4() { return value4; }
    public T5 getValue5() { return value5; }
    public T6 getValue6() { return value6; }
}
